<?php
/**
 * View.class.php
 * 
 * doitPHP视图类，用于完成对视图文件(仅限html文件)的编译及加载工作
 * @author tommy <streen003@gmail.com>
 * @copyright  Copyright (c) 2010 Tommy Software Studio
 * @link http://www.doitphp.com
 * @license New BSD License.{@link http://www.opensource.org/licenses/bsd-license.php}
 * @version $Id: View.class.php 1.0 2010-10-30 23:10:00Z tommy $
 * @package core
 * @since 1.0
 */

if (!defined('IN_DOIT')) {
	exit();
}

class View extends Base {
	
	/**
	 * 视图实例
	 * 
	 * @var object
	 */
	protected static $_instance;
	
	/**
	 * 视图目录
	 * 
	 * @var string
	 */
	public $view_dir;
	
	/**
	 * 视图编译缓存目录
	 * 
	 * @var string
	 */
	public $compile_dir;
	
	/**
	 * 模板标签左侧边限字符
	 * 
	 * @var string
	 */
	public $left_delimiter = '<!--{';
	
	/**
	 * 模板标签右侧边限字符
	 * 
	 * @var string
	 */
	public $right_delimiter = '}-->';
	
	/**
     * 模板参数信息
     *
     * @var array
     */
	protected $_options = array();
	
	/**
	 * widget的名称,默认为null
	 * 
	 * @var string
	 */
	public $widget;
	
	/**
	 * 视图布局
	 * 
	 * @var string
	 */
	public $layout;
	
	
	/**
	 * 构造函数
	 * 
	 * 初始化运行环境,或用于运行环境中基础变量的赋值
	 * @access public
	 * @return boolean;
	 */
	public function __construct() {
				
		//定义视图目录及编译目录
		$this->view_dir = VIEW_DIR;
		$this->compile_dir	= APP_ROOT . 'cache/views' . DIRECTORY_SEPARATOR;
	}
	
	/**
	 * 获取视图文件的路径
	 * 
	 * @access protected
	 * @param string $file_name	视图名. 注：不带后缀
	 * @return string	视图文件路径
	 */
	protected function get_view_file($file_name) {		
		
		return $this->view_dir . $file_name . '.html';
	}
	
	/**
	 * 获取视图编译文件的路径
	 * 
	 * @access protected
	 * @param string $file_name 视图名. 注:不带后缀
	 * @return string	编译文件路径
	 */
	protected function get_compile_file($file_name) {			
		
		return $this->compile_dir . $file_name . '.cache.php';
	}
	
	/**
	 * 生成视图编译文件
	 * 
	 * @access protected
	 * @param string $compile_file 编译文件名
	 * @param string $content	编译文件内容
	 * @return void
	 */
	protected function create_compile_file($compile_file, $content) {
		
		//分析编译文件目录
		$compile_dir = dirname($compile_file);
		
		if (!is_dir($compile_dir)) {
			mkdir($compile_dir, 0777);
		} else if (!is_writable($compile_dir)) {
			chmod($compile_dir, 0777);
		}
		
		$content = "<?php\r\nif(!defined('IN_DOIT')) exit(); ?>\r\n" . $content;
		
		return file_put_contents($compile_file, $content, LOCK_EX);		
	}
	
	/**
	 * 缓存重写分析
	 * 
	 * 判断缓存文件是否需要重新生成. 返回true时,为需要;返回false时,则为不需要
	 * @access protected
	 * @param string $view_file		视图文件名
	 * @param string $compile_file	视图编译文件名
	 * @return boolean
	 */
	protected function is_compile($view_file, $compile_file) {

		return (is_file($compile_file) && (filemtime($compile_file) >= filemtime($view_file))) ? false : true;
	}
	
	/**
	 * 设置视图变量
	 * 
	 * @access public
	 * @param mixed $key	视图变量名 
	 * @param string $value	视图变量数值
	 * @return mixed
	 */
	public function assign($key, $value = null) {
		
		//参数分析
		if(!$key) {
			return false;
		}
		
		//当$key为数组时
		if(is_array($key)) {
			foreach ($key as $k=>$v) {
				$this->_options[$k] = $v;
			} 
		} else {
			$this->_options[$key] = $value;
		}
		
		return true;
	}
	
	/**
	 * 分析视图文件名
	 * 
	 * @access protected
	 * @param mixed $file_name	视图名
	 * @return stirng
	 */
	protected function parse_file_name($file_name = null) {
		
		//当视图类在widget下运行时
		if ($this->widget) {
			return is_null($file_name) ? $this->widget : $file_name;
		}
		
		//当参数为空时，默认当前controller当前action所对应的视图文件
		if (is_null($file_name)) {
						
			//获取当前的controller及action
			$controller_id 	= doit::get_controller_id();
			$action_id 		= doit::get_action_id();
			//生成默认视图文件
			$file_name = $controller_id . '/' . $action_id;
			
		} else {
			
			//分析视图文件里是否调用非当前controller的视图文件
			if (strpos($file_name, '/') !==  false) {
				$file_name_array 	= explode('/', $file_name);
				$file_name     		= trim($file_name_array[0]) . '/' . trim($file_name_array[1]);
			} else {
				//当视图类在widget里运行时				
				$controller_id 	= doit::get_controller_id();
				$file_name = $controller_id . '/' . $file_name;							
			}
		}
		
		return $file_name;
	}
	
	/**
	 * 加载视图文件
	 * 
	 * 加载视图文件并对视图标签进行编译
	 * @access protected 
	 * @param string $view_file 	视图文件及路径
	 * @return string 				编译视图文件的内容
	 */
	protected function load_view_file($view_file) {
		
		//分析视图文件是否存在
		if (!is_file($view_file)) {
			trigger_error('The view file: ' . $view_file . ' is not exists!', E_USER_ERROR);
		}
		
		$view_content = file_get_contents($view_file);
		
		//编译视图标签
		return $this->handle_view_file($view_content);
	}
	
	/**
	 * 编译视图标签
	 * 
	 * @access protected
	 * @param string $view_content
	 * @return string	编译视图文件的内容
	 */
	protected function handle_view_file($view_content) {
		
		//参数分析
		if (!$view_content) {
			return false;
		}
		
		//正则表达式匹配的模板标签
		$regex_array = array(
		'#'.$this->left_delimiter.'\s*\$(.+?)\s*'.$this->right_delimiter.'#i',
		'#'.$this->left_delimiter.'\s*include\s+(.+?)\s*'.$this->right_delimiter.'#is',
		'#'.$this->left_delimiter.'\s*widget\s+(.+?)\s*'.$this->right_delimiter.'#is',
		'#'.$this->left_delimiter.'eval\s+(.+?)'.$this->right_delimiter.'#is',
		
		'#'.$this->left_delimiter.'\s?if\s+(.+?)\s?'.$this->right_delimiter.'#i',
		'#'.$this->left_delimiter.'\s?else\sif\s+(.+?)\s?'.$this->right_delimiter.'#i',
		'#'.$this->left_delimiter.'\s?else\s?'.$this->right_delimiter.'#i',
		'#'.$this->left_delimiter.'\s?\/if\s?'.$this->right_delimiter.'#i',
		
		'#'.$this->left_delimiter.'\s?loop\s+\$(.+?)\s+\$(\w+?)\s?'.$this->right_delimiter.'#i',
		'#'.$this->left_delimiter.'\s?loop\s+\$(.+?)\s+\$(\w+?)\s?=>\s?\$(\w+?)\s?'.$this->right_delimiter.'#i',
		'#'.$this->left_delimiter.'\s?\/loop\s?'.$this->right_delimiter.'#i',
		
		'#\?\>\s*\<\?php\s#s',
		);
		
		///替换直接变量输出
		$replace_array = array(
		"<?php echo \$\\1; ?>",
		"<?php include_once \$this->widget('\\1'); ?>",
		"<?php Controller::widget('\\1'); ?>",
		"<?php \\1 ?>",
		
		"<?php if (\\1) { ?>",
		"<?php } else if (\\1) { ?>",
		"<?php } else { ?>",
		"<?php } ?>",
		
		"<?php if (is_array(\$\\1)) { foreach (\$\\1 as \$\\2) { ?>",
		"<?php if (is_array(\$\\1)) { foreach (\$\\1 as \$\\2=>\$\\3) { ?>",
		"<?php } } ?>",
		
		" ",
		);
		
		return preg_replace($regex_array, $replace_array, $view_content);
	}

	/**
	 * 加载挂件视图
	 * 
	 * @access protected
	 * @param string $file_name		挂件文件名
	 * @return string				编译文件路径
	 */
	protected function widget($file_name) {
		
		//参数分析
		if (!$file_name) {
			return false;
		}
		
		//分析视图文件名
		$file_name = $this->parse_file_name($file_name);
		
		//获取视图文件及编译文件
		$view_file		= $this->get_view_file($file_name);
		$compile_file	= $this->get_compile_file($file_name);
		
		//分析视图编译文件是否需要重新生成
		if ($this->is_compile($view_file, $compile_file)) {			
			$view_content = $this->load_view_file($view_file);
			//重新生成编译缓存文件
			$this->create_compile_file($compile_file, $view_content);
		}
		
		return $compile_file;
	}		
	
	/**
	 * 显示视图文件
	 * 
	 * @access public
	 * @param string $file_name	视图名
	 * @return void
	 */
	public function display($file_name = null) {

		//分析视图变量
		if (!empty($this->_options)) {
			extract($this->_options, EXTR_PREFIX_SAME, 'data');
			//清空不必要的内存占用
			$this->_options = array();
		}		

		//判断layout视图变量
		if ($this->layout) {
			$layout_file 	= $this->view_dir . 'layout/' . $this->layout . '.html';
			$layout_state 	= is_file($layout_file) ? true : false;
		} else {
			$layout_state 	= false;
		}
		
		//分析视图文件名
		$file_name = $this->parse_file_name($file_name);
		
		//获取视图文件及编译文件
		$view_file		= $this->get_view_file($file_name);
		$compile_file	= $this->get_compile_file($file_name);				
		
		//分析视图编译文件是否需要重新生成
		if ($this->is_compile($view_file, $compile_file)) {			
			$view_content = $this->load_view_file($view_file);
			//重新生成编译缓存文件
			$this->create_compile_file($compile_file, $view_content);
		}

		if (!$layout_state) {
			//加载编译缓存文件
			include $compile_file;
		} else {
									
			//加载layout文件
			$layout_compile_file = $this->get_compile_file('layout/' . $this->layout);			
			if ($this->is_compile($layout_file, $layout_compile_file)) {
				//重新生成layout视图编译文件
				$layout_content = $this->load_view_file($layout_file);
				$this->create_compile_file($layout_compile_file, $layout_content);
			}
			
			//获取视图编译文件内容
			ob_start();
			include $compile_file;
			$content = ob_get_clean();
			
			//获取所要显示的页面的视图编译内容
			ob_start();
			include $layout_compile_file;
			echo ob_get_clean();
		}		
	}	
	
	/**
	 * 析构函数
	 * 
	 * 用于程序运行结束后"打扫战场"
	 * @access public
	 * @return void
	 */
	public function __destruct() {
		//清空不必要的内存占用
		$this->_options = array();
	}
	
	/**
	 * 单件模式调用方法
	 * 
	 * @static
	 * @var object
	 */
 	public static function getInstance(){

 		if (!self::$_instance instanceof self) {
 			self::$_instance = new self();
 		}
 			
		return self::$_instance;
	}
}